#!/bin/bash

set -e

echo "🚀 初始化 TMagic Vue 3 低代码平台模板仓库..."

# 创建目录结构
mkdir -p src/{core,components/{designer,renderer,business},composables,stores,types,views,router}

# =============== package.json ===============
cat > package.json <<EOF
{
  "name": "tmagic-vue-template",
  "version": "1.0.0",
  "description": "Vue 3 Low-Code Platform Template",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.4.0",
    "pinia": "^2.1.7",
    "vue-router": "^4.3.0",
    "element-plus": "^2.5.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.0",
    "typescript": "^5.3.0",
    "vite": "^5.0.0",
    "vue-tsc": "^1.8.0"
  }
}
EOF

# =============== tsconfig.json ===============
cat > tsconfig.json <<EOF
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
EOF

cat > tsconfig.node.json <<EOF
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}
EOF

# =============== vite.config.ts ===============
cat > vite.config.ts <<EOF
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})
EOF

# =============== src/main.ts ===============
cat > src/main.ts <<EOF
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { router } from '@/router'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// 注册业务组件（示例）
import { componentRegistry } from '@/core/ComponentRegistry'
import UserCard from '@/components/business/UserCard.vue'

componentRegistry.register('UserCard', UserCard, {
  name: 'UserCard',
  label: '用户卡片',
  icon: '👤',
  props: {
    avatar: 'https://via.placeholder.com/60',
    name: '张三',
    role: '前端工程师'
  },
  events: {
    click: [{ type: 'alert', params: { message: '查看 {{name}} 的详情' } }]
  }
})

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)
app.use(ElementPlus)
app.mount('#app')
EOF

# =============== src/App.vue ===============
cat > src/App.vue <<EOF
<template>
  <div class="app">
    <div class="header">
      <h2>✨ TMagic Vue 3</h2>
      <div>
        <ElButton @click="openPreview">👁️ 预览</ElButton>
        <ElButton type="primary" @click="store.saveToBackend">💾 保存</ElButton>
      </div>
    </div>
    <div class="editor">
      <ComponentLibrary />
      <div class="canvas">
        <Renderer :node="store.page" @select="store.selectNode" />
      </div>
      <PropsPanel />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ElButton } from 'element-plus'
import { useEditorStore } from '@/stores/editor'
import ComponentLibrary from '@/components/designer/ComponentLibrary.vue'
import Renderer from '@/components/renderer/Renderer.vue'
import PropsPanel from '@/components/designer/PropsPanel.vue'

const store = useEditorStore()

const openPreview = () => {
  localStorage.setItem('tmagic-preview-page', JSON.stringify(store.page))
  window.open('#/preview', '_blank')
}
</script>

<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; }
.app { padding: 16px; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.editor { display: flex; gap: 16px; height: calc(100vh - 80px); }
.canvas { flex: 1; border: 1px solid #ddd; padding: 16px; background: #fafafa; overflow: auto; }
</style>
EOF

# =============== src/router/index.ts ===============
cat > src/router/index.ts <<EOF
import { createRouter, createWebHashHistory } from 'vue-router'
import Preview from '@/views/Preview.vue'

const routes = [
  { path: '/preview', component: Preview }
]

export const router = createRouter({
  history: createWebHashHistory(),
  routes
})
EOF

# =============== src/core/ComponentRegistry.ts ===============
cat > src/core/ComponentRegistry.ts <<EOF
import type { Component } from 'vue'
import type { CustomComponentMeta } from '@/types/schema'

class ComponentRegistry {
  private components = new Map<string, { component: Component; meta: CustomComponentMeta }>()

  register(name: string, component: Component, meta: CustomComponentMeta) {
    this.components.set(name, { component, meta })
  }

  get(name: string) {
    return this.components.get(name)
  }

  getAll() {
    return Array.from(this.components.values())
  }

  getNames() {
    return Array.from(this.components.keys())
  }
}

export const componentRegistry = new ComponentRegistry()
EOF

# =============== src/types/schema.ts ===============
cat > src/types/schema.ts <<EOF
export type NodeType = 'page' | 'button' | 'input' | 'container' | 'grid' | 'tabs' | 'custom'

export type EventActionType = 'alert' | 'http' | 'navigate' | 'custom'

export interface EventAction {
  type: EventActionType
  params: Record<string, any>
}

export interface CustomComponentMeta {
  name: string
  label: string
  icon?: string
  props?: Record<string, any>
  events?: Record<string, EventAction[]>
}

export interface Node {
  id: string
  type: NodeType
  component?: string
  props: Record<string, any>
  events?: {
    [eventName: string]: EventAction[]
  }
  children?: Node[]
}
EOF

# =============== src/stores/editor.ts ===============
cat > src/stores/editor.ts <<EOF
import { defineStore } from 'pinia'
import { v4 as uuidv4 } from 'uuid'
import type { Node, NodeType } from '@/types/schema'

export const useEditorStore = defineStore('editor', {
  state: () => ({
    page: {
      id: 'page',
      type: 'page',
      props: {},
      children: []
    } as Node,
    selectedNodeId: null as string | null
  }),

  getters: {
    selectedNode: (state) => {
      if (!state.selectedNodeId) return null
      return findNodeById(state.page, state.selectedNodeId)
    }
  },

  actions: {
    selectNode(id: string) {
      this.selectedNodeId = id
    },

    addNode(type: NodeType, component?: string) {
      const newNode: Node = {
        id: uuidv4(),
        type,
        component,
        props:
          type === 'button'
            ? { text: '按钮' }
            : type === 'input'
            ? { placeholder: '请输入', value: '' }
            : type === 'container'
            ? { padding: '16px', bgColor: '#fafafa', border: true }
            : type === 'grid' || type === 'tabs'
            ? {}
            : type === 'custom' && component
            ? componentRegistry.get(component)?.meta.props || {}
            : {},
        events:
          type === 'button'
            ? { click: [{ type: 'alert', params: { message: '你好！' } }] }
            : type === 'custom' && component
            ? componentRegistry.get(component)?.meta.events
            : undefined,
        children: type === 'tabs' ? [{ id: uuidv4(), type: 'container', props: {}, children: [] }] : []
      }
      this.page.children.push(newNode)
    },

    saveToBackend() {
      console.log('保存页面:', JSON.stringify(this.page, null, 2))
      alert('已保存到控制台')
    }
  }
})

function findNodeById(node: Node, id: string): Node | null {
  if (node.id === id) return node
  if (node.children) {
    for (const child of node.children) {
      const found = findNodeById(child, id)
      if (found) return found
    }
  }
  return null
}

// Mock componentRegistry import (real one is in core/)
const componentRegistry = { get: () => ({ meta: { props: {}, events: {} } }) }
EOF

# =============== src/composables/useRenderer.ts ===============
cat > src/composables/useRenderer.ts <<EOF
import { getCurrentInstance, reactive, inject, provide } from 'vue'
import type { Node, EventAction } from '@/types/schema'

const RENDERER_CONTEXT_KEY = 'renderer-context'

export interface RendererContext {
  nodes: Record<string, Node>
  registerNode: (node: Node) => void
  unregisterNode: (id: string) => void
  getValue: (id: string, prop: string) => any
}

export function createRendererContext(): RendererContext {
  const nodes = reactive<Record<string, Node>>({})

  return {
    nodes,
    registerNode(node: Node) {
      nodes[node.id] = node
    },
    unregisterNode(id: string) {
      delete nodes[id]
    },
    getValue(id: string, prop: string) {
      return nodes[id]?.props?.[prop]
    }
  }
}

export function provideRendererContext() {
  const context = createRendererContext()
  provide(RENDERER_CONTEXT_KEY, context)
  return context
}

export function useRenderer() {
  const instance = getCurrentInstance()
  if (!instance) {
    throw new Error('useRenderer must be called in setup()')
  }

  const context = inject<RendererContext>(RENDERER_CONTEXT_KEY)
  if (!context) {
    throw new Error('Renderer context not provided')
  }

  const registerNode = (node: Node) => {
    context.registerNode(node)
  }

  const getContextValue = (id: string, prop: string) => {
    return context.getValue(id, prop)
  }

  const replaceVariables = (str: string, currentId: string): string => {
    return str.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
      const trimmed = expr.trim()
      if (trimmed === 'this') return ''
      if (trimmed.startsWith('this.')) {
        const prop = trimmed.slice(5)
        return context.getValue(currentId, prop) || ''
      }
      const [id, prop] = trimmed.split('.')
      return context.getValue(id, prop) || ''
    })
  }

  const executeEventActions = (actions: EventAction[], nodeId: string) => {
    for (const action of actions) {
      if (action.type === 'alert') {
        let message = action.params.message || ''
        message = replaceVariables(message, nodeId)
        alert(message)
      }
    }
  }

  return {
    registerNode,
    getContextValue,
    executeEventActions
  }
}
EOF

# =============== src/components/business/UserCard.vue ===============
cat > src/components/business/UserCard.vue <<EOF
<template>
  <div class="user-card">
    <img :src="props.avatar" :alt="props.name" />
    <h4>{{ props.name }}</h4>
    <p>{{ props.role }}</p>
    <ElButton size="small" @click="handleClick">查看详情</ElButton>
  </div>
</template>

<script setup lang="ts">
import { ElButton } from 'element-plus'

const props = defineProps<{
  avatar?: string
  name?: string
  role?: string
}>()

const emit = defineEmits<{ (e: 'click'): void }>()

const handleClick = () => {
  emit('click')
}
</script>

<style scoped>
.user-card {
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 16px;
  text-align: center;
  width: 200px;
  margin: 8px;
}
.user-card img {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  object-fit: cover;
}
</style>
EOF

# =============== src/components/renderer/Renderer.vue ===============
cat > src/components/renderer/Renderer.vue <<EOF
<template>
  <div v-if="node.type === 'page'" class="page-renderer">
    <Renderer
      v-for="child in node.children"
      :key="child.id"
      :node="child"
      @select="$emit('select', child.id)"
    />
  </div>

  <ElButton
    v-else-if="node.type === 'button'"
    :style="{ margin: '8px' }"
    @click="handleClick"
    @click.stop="$emit('select', node.id)"
  >
    {{ node.props.text }}
  </ElButton>

  <ElInput
    v-else-if="node.type === 'input'"
    v-model="node.props.value"
    :placeholder="node.props.placeholder"
    :style="{ margin: '8px' }"
    @click.stop="$emit('select', node.id)"
  />

  <Container
    v-else-if="node.type === 'container'"
    :node="node"
    @select="$emit('select', $event)"
  />

  <Grid
    v-else-if="node.type === 'grid'"
    :node="node"
    @select="$emit('select', $event)"
  />

  <Tabs
    v-else-if="node.type === 'tabs'"
    :node="node"
    @select="$emit('select', $event)"
  />

  <component
    v-else-if="node.type === 'custom'"
    :is="dynamicComponent"
    v-bind="node.props"
    v-on="eventListeners"
    @click.stop="$emit('select', node.id)"
  />

  <div v-else @click.stop="$emit('select', node.id)">
    <Renderer
      v-for="child in node.children"
      :key="child.id"
      :node="child"
      @select="$emit('select', child.id)"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { ElButton, ElInput } from 'element-plus'
import type { Node } from '@/types/schema'
import Container from './Container.vue'
import Grid from './Grid.vue'
import Tabs from './Tabs.vue'
import { componentRegistry } from '@/core/ComponentRegistry'
import { useRenderer } from '@/composables/useRenderer'

const props = defineProps<{ node: Node }>()
const emit = defineEmits<{ (e: 'select', id: string): void }>()

const { registerNode, executeEventActions } = useRenderer()

onMounted(() => {
  registerNode(props.node)
})

const dynamicComponent = computed(() => {
  if (props.node.type !== 'custom') return null
  const reg = componentRegistry.get(props.node.component!)
  return reg?.component || null
})

const eventListeners = computed(() => {
  const listeners: Record<string, Function> = {}
  if (props.node.events) {
    for (const eventName in props.node.events) {
      listeners[eventName] = (...args: any[]) => {
        executeEventActions(props.node.events![eventName], props.node.id)
      }
    }
  }
  return listeners
})

const handleClick = () => {
  if (props.node.events?.click) {
    executeEventActions(props.node.events.click, props.node.id)
  }
}
</script>
EOF

# =============== 其他 renderer 组件 (Container, Grid, Tabs) ===============
cat > src/components/renderer/Container.vue <<EOF
<template>
  <div class="tm-container" :style="style">
    <Renderer
      v-for="child in node.children"
      :key="child.id"
      :node="child"
      @select="$emit('select', child.id)"
    />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { Node } from '@/types/schema'
import Renderer from './Renderer.vue'

const props = defineProps<{ node: Node }>()
const emit = defineEmits<{ (e: 'select', id: string): void }>()

const style = computed(() => ({
  padding: props.node.props.padding || '16px',
  backgroundColor: props.node.props.bgColor || 'transparent',
  border: props.node.props.border ? '1px dashed #ccc' : 'none'
}))
</script>

<style scoped>
.tm-container {
  min-height: 60px;
  margin: 8px 0;
}
</style>
EOF

cat > src/components/renderer/Grid.vue <<EOF
<template>
  <div class="tm-grid">
    <div class="tm-grid-col">
      <Renderer
        v-for="child in leftChildren"
        :key="child.id"
        :node="child"
        @select="$emit('select', child.id)"
      />
    </div>
    <div class="tm-grid-col">
      <Renderer
        v-for="child in rightChildren"
        :key="child.id"
        :node="child"
        @select="$emit('select', child.id)"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import type { Node } from '@/types/schema'
import Renderer from './Renderer.vue'

const props = defineProps<{ node: Node }>()
const emit = defineEmits<{ (e: 'select', id: string): void }>()

const children = props.node.children || []
const mid = Math.ceil(children.length / 2)
const leftChildren = children.slice(0, mid)
const rightChildren = children.slice(mid)
</script>

<style scoped>
.tm-grid {
  display: flex;
  gap: 16px;
  margin: 16px 0;
}
.tm-grid-col {
  flex: 1;
  min-width: 0;
}
</style>
EOF

cat > src/components/renderer/Tabs.vue <<EOF
<template>
  <ElTabs v-model="activeTab" type="card">
    <ElTabPane
      v-for="(child, index) in node.children"
      :key="child.id"
      :label="child.props.tabLabel || \`Tab \${index + 1}\`"
      :name="child.id"
    >
      <Renderer
        v-for="grandChild in child.children"
        :key="grandChild.id"
        :node="grandChild"
        @select="$emit('select', grandChild.id)"
      />
    </ElTabPane>
  </ElTabs>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ElTabs, ElTabPane } from 'element-plus'
import type { Node } from '@/types/schema'
import Renderer from './Renderer.vue'

const props = defineProps<{ node: Node }>()
const emit = defineEmits<{ (e: 'select', id: string): void }>()

const activeTab = ref(props.node.children?.[0]?.id || '')
</script>
EOF

# =============== ComponentLibrary.vue ===============
cat > src/components/designer/ComponentLibrary.vue <<EOF
<template>
  <div class="component-library">
    <h3>组件库</h3>
    <div class="components">
      <div
        v-for="item in allComponents"
        :key="item.type + (item.component || '')"
        class="component-item"
        draggable="true"
        @dragstart="onDragStart(item)"
      >
        {{ item.icon || '' }} {{ item.label }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useEditorStore } from '@/stores/editor'
import { componentRegistry } from '@/core/ComponentRegistry'

const store = useEditorStore()

const customComponents = computed(() => {
  return componentRegistry.getAll().map(item => ({
    type: 'custom',
    component: item.meta.name,
    label: item.meta.label,
    icon: item.meta.icon || '🧩'
  }))
})

const allComponents = computed(() => [
  { type: 'button', label: '按钮' },
  { type: 'input', label: '输入框' },
  { type: 'container', label: '容器' },
  { type: 'grid', label: '栅格' },
  { type: 'tabs', label: '选项卡' },
  ...customComponents.value
])

const onDragStart = (item: any) => {
  const data = JSON.stringify({
    type: item.type,
    component: item.component
  })
  ;(event as DragEvent).dataTransfer?.setData('text/plain', data)
}
</script>

<style scoped>
.component-library {
  width: 200px;
  border-right: 1px solid #ddd;
  padding: 16px;
}
.components { display: flex; flex-direction: column; gap: 8px; }
.component-item {
  padding: 8px;
  border: 1px solid #eee;
  border-radius: 4px;
  cursor: move;
}
</style>
EOF

# =============== PropsPanel.vue ===============
cat > src/components/designer/PropsPanel.vue <<EOF
<template>
  <ElCard header="事件配置" v-if="store.selectedNode">
    <ElForm label-position="top">
      <ElFormItem label="点击事件">
        <ElButton size="small" @click="addClickAction">➕ 添加动作</ElButton>
        <div v-for="(action, index) in clickActions" :key="index" class="action-item">
          <ElSelect v-model="action.type" placeholder="动作类型">
            <ElOption value="alert" label="弹窗提示" />
            <ElOption value="http" label="HTTP 请求" />
          </ElSelect>
          <ElInput
            v-if="action.type === 'alert'"
            v-model="action.params.message"
            placeholder="消息内容（支持 {{input1.value}}）"
          />
          <div class="action-controls">
            <ElButton type="danger" size="small" @click="removeAction(index)">🗑️</ElButton>
          </div>
        </div>
      </ElFormItem>
    </ElForm>
  </ElCard>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { ElCard, ElForm, ElFormItem, ElButton, ElSelect, ElOption, ElInput } from 'element-plus'
import { useEditorStore } from '@/stores/editor'

const store = useEditorStore()

const clickActions = computed({
  get() {
    return store.selectedNode?.events?.click || []
  },
  set(value) {
    if (store.selectedNode) {
      store.selectedNode.events = {
        ...store.selectedNode.events,
        click: value
      }
    }
  }
})

const addClickAction = () => {
  clickActions.value.push({ type: 'alert', params: { message: '' } })
}

const removeAction = (index: number) => {
  clickActions.value.splice(index, 1)
}
</script>

<style scoped>
.action-item {
  display: flex;
  gap: 8px;
  margin-top: 8px;
  align-items: center;
}
.action-controls {
  margin-left: auto;
}
</style>
EOF

# =============== Preview.vue ===============
cat > src/views/Preview.vue <<EOF
<template>
  <div class="preview-page">
    <Renderer :node="page" />
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import { useEditorStore } from '@/stores/editor'
import Renderer from '@/components/renderer/Renderer.vue'
import { provideRendererContext } from '@/composables/useRenderer'

const store = useEditorStore()
provideRendererContext()

onMounted(() => {
  const saved = localStorage.getItem('tmagic-preview-page')
  if (saved) {
    store.page = JSON.parse(saved)
  }
})

const page = store.page
</script>

<style>
.preview-page {
  padding: 20px;
  background: #fff;
  min-height: 100vh;
}
</style>
EOF

# =============== 初始化 Git ===============
git init
git add .
git commit -m "feat: 初始化 TMagic Vue 3 低代码平台模板"

# =============== 完成提示 ===============
echo "✅ 项目初始化完成！"
echo "👉 运行以下命令启动开发服务器："
echo "   npm install"
echo "   npm run dev"
echo ""
echo "📌 此仓库可直接设为 GitHub Template Repository"